package pCloud.service;

import java.io.PrintStream;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import com.sun.tools.javac.code.Attribute.Array;
import com.sun.xacml.attr.StringAttribute;
import com.sun.xacml.ctx.Attribute;
import com.sun.xacml.ctx.RequestCtx;
import com.sun.xacml.ctx.Result;
import com.sun.xacml.ctx.Subject;

import pCloud.PCloudAttributeAndType;
import pCloud.message.ClientRequest;
import pCloud.message.ClientResponse;
import pCloud.message.DatabaseReply;
import pCloud.message.ReplyMessage;
import pCloud.message.RequestFile;
import pCloud.message.RequestedResource;
import pCloud.message.SocketFile;

/**
 * Utility methods for the proxy server
 * 
 */
public class ProxyHelper {

	/**
	 * Constructing the request file, to be sent to the data server for
	 * processing
	 * 
	 * @throws URISyntaxException
	 */
	public SocketFile createRequest(int requestId, String joiningColumn, String name, String role,
			RequestedResource rr) throws URISyntaxException {
		RequestCtx request = new RequestCtx(setupSubjects(name, role),
				setupResource(rr), setupAction(rr), new HashSet());
		RequestFile rf = new RequestFile(requestId, joiningColumn);
		rf.initializeContentFromRequestCtx(request);
		return rf;
	}

	/**
	 * Once received all the replies from the data servers, join them before
	 * sending back to the client. The joining condition is specified by the
	 * joining column in
	 * 
	 * This is called ONLY when there is joining to be done.
	 */
	public ReplyMessage mergeResponses(ClientRequest request,
			ClientResponse responses) {

		ReplyMessage emptyReply = new ReplyMessage(request.getRequestId(),
				Result.DECISION_INDETERMINATE, null);

		HashMap<InetSocketAddress, ReplyMessage> replies = responses
				.getReplies();

		HashMap<InetSocketAddress, String> joiningCols = request
				.getJoinColumns();

		Object[] hosts = replies.keySet().toArray();
		String joiningColName = joiningCols.get((InetSocketAddress) hosts[0]);

		// 1. If any reply doesn't contain any data, return empty reply
		InetSocketAddress server;
		DatabaseReply data;

		// IDs of all the columns after joining
		ArrayList<String> columnIds = new ArrayList<String>();
		ArrayList<String> temp;
		DatabaseReply replyContent = new DatabaseReply(joiningColName);

		for (int i = 0; i < hosts.length; i++) {
			server = (InetSocketAddress) hosts[i];
			data = replies.get(server).getDatabaseReply();
			// return empty if there's no data in one of the response
			if (data == null || data.getRowsOfValues().size() == 0)
				return emptyReply;

			temp = data.getNonJoiningColumns();
			for (int j = 0; j < temp.size(); j++) {
				columnIds.add(temp.get(j));
				replyContent.addColumnId(temp.get(j));
			}
		}

		// JOINING:
		// for every value in the joining column of the 1st reply (from the 1st
		// host), check
		// if that value exists in all other replies. If yes, adding the value
		// from all other columns
		// to the output.
		data = replies.get((InetSocketAddress) hosts[0]).getDatabaseReply();

		
		// get all values in the joining column of that response
		Object[] joiningValues = data.getRowsOfValues().keySet().toArray();
		Arrays.sort(joiningValues);
		Object val;
		boolean isJoinable = true;
		for (int i = 0; i < joiningValues.length; i++) {
			val = joiningValues[i];
			// checking if all other responses form other hosts have this value
			isJoinable = true;
			for (int j = 1; j < hosts.length; j++) {
				if (!replies.get((InetSocketAddress) hosts[j])
						.getDatabaseReply().getRowsOfValues().containsKey(val)) {
					isJoinable = false;
					break;
				}
			}
			if (isJoinable) {
				// first, add the val
				replyContent.addKeyValue(val);
				// then, add values from all the other columns
				for (int j = 0; j < hosts.length; j++) {
					ArrayList otherColVals = replies
							.get((InetSocketAddress) hosts[j])
							.getDatabaseReply().getRowsOfValues().get(val);
					for (int k = 0; k < otherColVals.size(); k++) {
						replyContent.addValueToRow(val, otherColVals.get(k));
					}
				}
			}
		}

		ReplyMessage returnedReplyMsg = new ReplyMessage(
				request.getRequestId(), Result.DECISION_PERMIT, replyContent);

		// Finnaly, adding all the matching policies
		for (int i = 0; i < hosts.length; i++) {
			returnedReplyMsg.addMatchingPolicyIds(replies.get(
					(InetSocketAddress) hosts[i]).getMatchingPolicyIds());
		}

		return returnedReplyMsg;
	}

	private Set setupSubjects(String name, String role)
			throws URISyntaxException {
		HashSet attributes = new HashSet();

		URI subjectRole = new URI(PCloudAttributeAndType.SUBJECT_ROLE_ID);
		StringAttribute subjectRoleValue = new StringAttribute(role);

		URI subjectName = new URI(PCloudAttributeAndType.SUBJECT_NAME_ID);
		StringAttribute subjectNameValue = new StringAttribute(name);

		attributes
				.add(new Attribute(subjectRole, null, null, subjectRoleValue));
		// ...and the second with the subject's group membership
		attributes
				.add(new Attribute(subjectName, null, null, subjectNameValue));

		// bundle the attributes in a Subject with the default category
		HashSet subjects = new HashSet();
		subjects.add(new Subject(attributes));

		return subjects;
	}

	private Set setupResource(RequestedResource rr) throws URISyntaxException {
		HashSet resource = new HashSet();

		URI resourceID = new URI(PCloudAttributeAndType.RESOURCE_DATABASE_ID);
		StringAttribute resourceValue = new StringAttribute(rr.getDatabaseId());

		resource.add(new Attribute(resourceID, null, null, resourceValue));

		resourceID = new URI(PCloudAttributeAndType.RESOURCE_TABLE_ID);
		resourceValue = new StringAttribute(rr.getTableId());
		resource.add(new Attribute(resourceID, null, null, resourceValue));

		resourceID = new URI(PCloudAttributeAndType.RESOURCE_COLUMN_ID);
		ArrayList<String> columnIds = rr.getColumnIds();
		for (int i = 0; i < columnIds.size(); i++) {
			resourceValue = new StringAttribute(columnIds.get(i));
			resource.add(new Attribute(resourceID, null, null, resourceValue));
		}

		return resource;
	}

	private Set setupAction(RequestedResource rr) throws URISyntaxException {
		HashSet action = new HashSet();

		// this is a standard URI that can optionally be used to specify
		// the action being requested
		URI actionId = new URI(PCloudAttributeAndType.ACTION_ID);
		StringAttribute actionValue = new StringAttribute(rr.getAction());

		// create the action
		action.add(new Attribute(actionId, null, null, actionValue));

		return action;
	}

}
